
//  Copyright (C) 2009-2012 Luca Piccioni
// 
//  This program is free software: you can redistribute it and/or modify
//  it under the terms of the GNU General Public License as published by
//  the Free Software Foundation, either version 3 of the License, or
//  (at your option) any later version.
// 
//  This program is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU General Public License for more details.
// 
//  You should have received a copy of the GNU General Public License
//  along with this program.  If not, see <http://www.gnu.org/licenses/>.

using System;

namespace Derm.Render
{
	/// <summary>
	/// Generic render state.
	/// </summary>
	public abstract class RenderState : IRenderState
	{
		#region Constructors

		/// <summary>
		/// Construct a RenderState.
		/// </summary>
		protected RenderState()
		{

		}

		/// <summary>
		/// Construct a RenderState, specifying whether it is inheritable.
		/// </summary>
		/// <param name="inheritable"></param>
		protected RenderState(bool inheritable)
		{
			// Inheritable flag
			mInheritable = inheritable;
		}

		#endregion

		#region State Factory

		/// <summary>
		/// Factory method for getting the default RenderState of the specified type.
		/// </summary>
		/// <param name="stateId">
		/// A <see cref="System.String"/> that identify the state type.
		/// </param>
		public static RenderState GetDefaultState(string stateId)
		{
			if (stateId == null)
				throw new ArgumentNullException("stateId");

			if      (stateId == BlendState.StateId)
				return (BlendState.DefaultState);
			else if (stateId == DepthTestState.StateId)
				return (DepthTestState.DefaultState);
			else
				throw new NotSupportedException("not supported default state for " + stateId);
		}

		/// <summary>
		/// Factory method for getting the current RenderState of the specific type.
		/// </summary>
		/// <param name="ctx">
		/// A <see cref="RenderContext"/> that determines the state. It has to be current on the calling
		/// thread.
		/// </param>
		/// <param name="stateId">
		/// A <see cref="System.String"/> that identify the state type.
		/// </param>
		public static RenderState GetCurrentState(RenderContext ctx, string stateId)
		{
			if (ctx == null)
				throw new ArgumentNullException("ctx");
			if (stateId == null)
				throw new ArgumentNullException("stateId");
			if (ctx.DebugEnabled) {
				if (ctx.IsCurrent() == false)
					throw new InvalidOperationException("no current context");
			}

			if      (stateId == BlendState.StateId)
				return (new BlendState(ctx));
			else if (stateId == DepthTestState.StateId)
				return (new DepthTestState(ctx));
			else
				throw new NotSupportedException("not supported current state for " + stateId);
		}

		#endregion

		#region Equality Operators

		/// <summary>
		/// Compare two RenderState for equality
		/// </summary>
		/// <param name="state1">
		/// A <see cref="RenderState"/> to compare with <paramref name="state2"/>.
		/// </param>
		/// <param name="state2">
		/// A <see cref="RenderState"/> to compare with <paramref name="state1"/>.
		/// </param>
		/// <returns>
		/// It returns true if <paramref name="state1"/> equals to <paramref name="state2"/>, otherwise false.
		/// </returns>
		public static bool operator==(RenderState state1, RenderState state2)
		{
			return (Equals(state1, state2));
		}

		/// <summary>
		/// Compare two RenderState for equality
		/// </summary>
		/// <param name="state1">
		/// A <see cref="RenderState"/> to compare with <paramref name="state2"/>.
		/// </param>
		/// <param name="state2">
		/// A <see cref="RenderState"/> to compare with <paramref name="state1"/>.
		/// </param>
		/// <returns>
		/// It returns true if <paramref name="state1"/> equals to <paramref name="state2"/>, otherwise false.
		/// </returns>
		public static bool operator!=(RenderState state1, RenderState state2)
		{
			return (!Equals(state1, state2));
		}

		#endregion

		#region IRenderState Implementation

		/// <summary>
		/// The identifier of this RenderState.
		/// </summary>
		public abstract string Id { get; }

		/// <summary>
		/// Flag indicating whether the state is context-bound.
		/// </summary>
		public abstract bool IsContextBound { get; }

		/// <summary>
		/// Flag indicating whether this state shall be inherited from nested states.
		/// </summary>
		public bool Inheritable { get { return (mInheritable); } protected set { mInheritable = value; } }

		/// <summary>
		/// Apply this RenderState.
		/// </summary>
		/// <param name="ctx">
		/// A <see cref="RenderContext"/> which has defined the shader program <paramref name="shaderProgram"/>.
		/// </param>
		/// <param name="shaderProgram">
		/// The <see cref="ShaderProgram"/> which has the state set.
		/// </param>
		public abstract void ApplyState(RenderContext ctx, ShaderProgram shaderProgram);

		/// <summary>
		/// Merge this state with another one.
		/// </summary>
		/// <param name="ctx">
		/// A <see cref="RenderContext"/> used for merging. It declares the renderer capabilities.
		/// </param>
		/// <param name="state">
		/// A <see cref="RenderState"/> having the same <see cref="Id"/> of this state. If this state depends
		/// on the information of <paramref name="state"/>, this is the entry point for modify this state.
		/// </param>
		public virtual void Merge(RenderContext ctx, RenderState state) { }

		/// <summary>
		/// Indicates whether the current object is equal to another object of the same type.
		/// </summary>
		/// <param name="other">
		/// A <see cref="RenderState"/> to compare to this RenderState.
		/// </param>
		/// <returns>
		/// It returns true if the current object is equal to <paramref name="other"/>.
		/// </returns>
		/// <remarks>
		/// <para>
		/// This method test only whether <paramref name="other"/> type equals to this type.
		/// </para>
		/// </remarks>
		/// <exception cref="ArgumentNullException">
		/// This exception is thrown if the parameter <paramref name="other"/> is null.
		/// </exception>
		public virtual bool Equals(IRenderState other)
		{
			if (other == null)
				throw new ArgumentNullException("other");

			return (other.Id != Id);
		}

		/// <summary>
		/// Performs a deep copy of this <see cref="IRenderState"/>.
		/// </summary>
		/// <returns>
		/// It returns the equivalent of this <see cref="IRenderState"/>, but all objects referenced
		/// are not referred by both instances.
		/// </returns>
		public virtual IRenderState Copy()
		{
			return ((IRenderState) MemberwiseClone());
		}

		/// <summary>
		/// Flag indicating whether this state is inheritable.
		/// </summary>
		private bool mInheritable;

		#endregion

		#region Object Overrides

		/// <summary>
		/// Determines whether the specified <see cref="T:System.Object"/> is equal to the current <see cref="T:System.Object"/>.
		/// </summary>
		/// <param name="obj">
		/// The <see cref="T:System.Object"/> to compare with the current <see cref="T:System.Object"/>.
		/// </param>
		/// <returns>
		/// It returns true if the specified <see cref="T:System.Object"/> is equal to the current <see cref="T:System.Object"/>; otherwise, false.
		/// </returns>
		public override bool Equals(object obj)
		{
			if (ReferenceEquals(null, obj))
				return (false);
			if (ReferenceEquals(this, obj))
				return (true);

			if (obj.GetType().IsSubclassOf(typeof(RenderState)) == false)
				return (false);

			return Equals((RenderState) obj);
		}

		/// <summary>
		/// Serves as a hash function for a particular type. <see cref="M:System.Object.GetHashCode"/> is suitable for
		/// use in hashing algorithms and data structures like a hash table.
		/// </summary>
		/// <returns>
		/// A hash code for the current <see cref="T:System.Object"/>.
		/// </returns>
		public override int GetHashCode()
		{
			return (Id.GetHashCode());
		}

		#endregion
	}
}
